package org.redis.cluster.extension;

import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.redis.cluster.Constants;
import org.redis.cluster.OrderSorter;
import org.redis.cluster.extension.exception.ExtensionException;
import org.redis.cluster.extension.helper.ExtensionLoaderHelper;
import org.redis.cluster.util.Assert;
import org.redis.cluster.util.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SimpleExtensionLoader implements ExtensionLoader {
	
	private final static String DEFAULT_EXTENSION_CONFIG=String.format("META-INF/%s/extensions", Constants.NAME);
	
	protected final Logger logger=LoggerFactory.getLogger(getClass());
	
	private Map<String,Map<String,Class<?>>> extensionClassMapping;
	
	private final Map<Class<?>,Object> instanceCache=new ConcurrentHashMap<Class<?>,Object>();
	
	private String config=DEFAULT_EXTENSION_CONFIG;
	
	@Override
	public <T> Collection<T> load(Class<T> type) {
		List<Class<?>> classes=getExtensionClasses(type);
		
		if(CollectionUtils.isEmpty(classes)) {
			return Collections.emptyList();
		}
		
		List<T> result=new ArrayList<T>(classes.size());
		for(Class<?> clazz:classes) {
			try {
				T instance=(T) getInstance(clazz);
				result.add(instance);
			}
			catch(Exception e) {
				throw new ExtensionException(e);
			}
		}
		
		OrderSorter.sort(result);
		return result;
	}

	protected <T> List<Class<?>> getExtensionClasses(Class<T> type) {
		List<Class<?>> classes=new ArrayList<Class<?>>();
		for(Map<String,Class<?>> extensionClasses:extensionClassMapping.values()) {
			for(Class<?> clazz:extensionClasses.values()) {
				if(isAssignableFrom(type, clazz, true) || isAssignableFrom(type, clazz, false)) {
					classes.add(clazz);
				}
			}
		}
		return classes;
	}
	
	@Override
	public <T> T load(Class<T> type, Object... parameters) {
		Collection<Class<?>> classes=getExtensionClasses(type);
		if(CollectionUtils.isEmpty(classes)) {
			return null;
		}
		
		return (T) getInstance(classes.iterator().next(),parameters);
	}
	
	@Override
	public <T> T load(String name, Class<T> type, Object... parameters) {
		if(StringUtils.isBlank(name)) {
			return load(type,parameters);
		}
		
		T result=doLoad(name, type, parameters);
		if(result==null) {
			if(logger.isDebugEnabled()) {
				logger.debug("没有找到 {} 扩展,尝试寻找默认扩展",name);
			}
			
			return load(type,parameters);
		}
		
		return result;
	}
	
	protected <T> T doLoad(String name,Class<T> type,Object...parameters) {
		Class<?> clazz=getExtensionClass(name,type,true);
		if(clazz==null) {
			clazz=getExtensionClass(name, type, false);
		}
		
		if(clazz==null) {
			return null;
		}
		
		T instance=null;
		try {
			if(ArrayUtils.isNotEmpty(parameters)) {
				instance=(T) getInstance(clazz, parameters);
			}
			else {
				instance=(T) getInstance(clazz);
			}
		}
		catch(Exception e) {
			throw new ExtensionException(e);
		}
		
		return instance;
	}
	
	protected <T> T getInstance(Class<T> type,Object...parameters) {
		if(instanceCache.containsKey(type)) {
			return (T) instanceCache.get(type);
		}
		
		Class<?> parameterTypes[]=new Class<?>[parameters.length];
		
		for(int i=0;i<parameters.length;i++) {
			parameterTypes[i]=parameters[i].getClass();
		}
		
		Constructor<?> constructor=null;
		Constructor<?> constructors[]=type.getConstructors();
		for(Constructor<?> tempConstructor:constructors) {
			if(tempConstructor.getParameterTypes().length==parameters.length) {
				constructor=tempConstructor;
				break;
			}
		}
		
		Assert.notNull(constructor,"%s 的构造方法不存在 %s(%s)",type.getName(),type.getSimpleName(),ArrayUtils.toString(parameterTypes));
		
		try {
			T instance=ClassUtil.newInstance(type, constructor, parameters);
			instanceCache.putIfAbsent(type, instance);
			return getInstance(type, parameters);
		}
		catch(Exception e) {
			throw new ExtensionException(e);
		}
	}
	
	protected <T> T getInstance(Class<T> type) {
		if(instanceCache.containsKey(type)) {
			return (T) instanceCache.get(type);
		}
		
		try {
			T instance=ClassUtil.newInstance(type);
			instanceCache.putIfAbsent(type, instance);
			return getInstance(type);
		}
		catch(Exception e) {
			throw new ExtensionException(e);
		}
	}
	
	protected Class<?> getExtensionClass(String name,Class<?> type,boolean isExact) {
		for(Entry<String,Map<String,Class<?>>> resourceEntry:extensionClassMapping.entrySet()) {
			for(Entry<String,Class<?>> entry:resourceEntry.getValue().entrySet()) {
				if(!entry.getKey().equals(name)) {
					continue;
				}
				
				Class<?> clazz=entry.getValue();
				if(isAssignableFrom(type,clazz,isExact)) {
					return clazz;
				}
			}
		}
		
		return null;
	}
	
	protected boolean isAssignableFrom(Class<?> target,Class<?> source,boolean isExact) {
		if(isExact) {
			return target.equals(source);
		}
		else {
			return target.isAssignableFrom(source);
		}
	}
	
	@Override
	public void init() {
		this.extensionClassMapping=initClassMapping();
	}
	
	protected Map<String,Map<String,Class<?>>> initClassMapping() {
		if(logger.isDebugEnabled()) {
			logger.debug("准备读取 - {}",config);
		}
		
		Enumeration<URL> urls=null;
		try {
			urls=getClass().getClassLoader().getResources(config);
		}
		catch(Exception e) {
			throw new ExtensionException(e);
		}
		
		if(urls==null) {
			return MapUtils.EMPTY_MAP;
		}
		
		Map<String,Map<String,Class<?>>> result=new HashMap<String,Map<String,Class<?>>>();
		
		try {
			while(urls.hasMoreElements()) {
				URL url=urls.nextElement();
				
				if(logger.isDebugEnabled()) {
					logger.debug("准备读取扩展 - {}",url);
				}
				
				Map<String, Class<?>> mapping=doReadMappings(url);
				if(MapUtils.isEmpty(mapping)) {
					continue;
				}
				
				result.put(url.toString(), mapping);
			}
		}
		catch(Exception e) {
			throw new ExtensionException(e);
		}
		
		return result;
	}
	
	protected Map<String,Class<?>> doReadMappings(URL url) throws Exception {
		return ExtensionLoaderHelper.read(url);
	}

	@Override
	public void destroy() {
	}
	
	public void setConfig(String config) {
		this.config = config;
	}
	public String getConfig() {
		return config;
	}
}